﻿using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Castle.DynamicProxy;
using Elf.ServiceModel.Interfaces;
using Elf.Common;
using Castle.MicroKernel.Registration;

namespace Elf.ServiceModel
{
    /// <summary>
    /// The WCF interceptor, accepts the service contract as a generic parameter.
    /// </summary>
    /// <typeparam name="TInterface"></typeparam>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
    public class WcfInterceptor<TInterface> : IWcfInterceptor<TInterface> where TInterface : class
    {
        #region Fields
        private ChannelFactory<TInterface> _factory = null;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="WcfInterceptor&lt;TInterface&gt;"/> class.
        /// </summary>
        public WcfInterceptor()
        {
            Initialize();
            _factory = new ChannelFactory<TInterface>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WcfInterceptor&lt;TInterface&gt;"/> class.
        /// </summary>
        /// <param name="endpointConfigName">Name of the endpoint config.</param>
        public WcfInterceptor(string endpointConfigName)
        {
            Initialize();
            _factory = new ChannelFactory<TInterface>(endpointConfigName);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WcfInterceptor&lt;TInterface&gt;"/> class.
        /// </summary>
        /// <param name="binding">The binding.</param>
        /// <param name="address">The address.</param>
        public WcfInterceptor(Binding binding, EndpointAddress address)
        {
            Initialize();
            _factory = new ChannelFactory<TInterface>(binding, address);
        }
        #endregion

        #region Initialization
        /// <summary>
        /// Initializes this instance.
        /// </summary>
        private static void Initialize()
        {
            // Check if not allready registered WCF Client Invoker
            var components = DependencyFactory.Instance.Container.Kernel.GetAssignableHandlers(typeof(IWcfClientInvoker));

            if (components.Length == 0)
            {
                DependencyFactory.Instance.Container.Register(Component.For<IWcfClientInvoker>().ImplementedBy<WcfClientInvoker>());
            }
        }
        #endregion

        #region Interception
        /// <summary>
        /// Intercepts the service call.
        /// </summary>
        /// <param name="invocation">The invocation.</param>
        public virtual void Intercept(IInvocation invocation)
        {
            if (invocation == null) throw new ArgumentNullException("invocation");

            TInterface channel = CreateChannel();

            invocation.ReturnValue = DependencyFactory.Instance.Container.Resolve<IWcfClientInvoker>().Invoke(invocation, (ICommunicationObject)channel, 3, 100, invocation.Arguments);

            CloseChannel(channel);
        }

        /// <summary>
        /// Creates the channel.
        /// </summary>
        /// <returns></returns>
        private TInterface CreateChannel()
        {
            return _factory.CreateChannel();
        }

        /// <summary>
        /// Closes the channel.
        /// </summary>
        /// <param name="channel">The channel.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Swallow on purpose")]
        private void CloseChannel(TInterface channel)
        {
            if (channel != null)
            {
                var communicationObject = (ICommunicationObject)channel;

                try
                {
                    if (communicationObject.State != CommunicationState.Faulted)
                    {
                        if (communicationObject.State != CommunicationState.Closed)
                        {
                            communicationObject.Close();
                        }
                    }
                    else
                    {
                        communicationObject.Abort();
                    }
                }
                catch (Exception)
                {
                    try
                    {
                        communicationObject.Abort();
                    }
                    catch { } //Swallow on purpose
                }
            }
        }
        #endregion
    }
}
